﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Koopakiller.Numerics.Resources;

namespace Koopakiller.Numerics
{
    /// <summary>
    /// Represents a vector or a point.
    /// </summary>
    public struct Vector
    {
        #region .ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="Vector"/> structure.
        /// </summary>
        /// <param name="values">The values of dimensions.</param>
        public Vector(params double[] values)
        {
            this.data = values;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Vector"/> structure.
        /// </summary>
        /// <param name="values">The values of dimensions.</param>
        public Vector(IEnumerable<double> values)
        {
            this.data = values.ToArray();
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Vector"/> structure.
        /// </summary>
        /// <param name="dimensions">The count of dimensions.</param>
        public Vector(int dimensions)
        {
            this.data = new double[dimensions];
        }

        #endregion

        double[] data;

        /// <summary>
        /// Gets or sets the value of a dimension with the index.
        /// </summary>
        /// <param name="index">The index of the dimension.</param>
        /// <returns>The value in the dimension.</returns>
        public double this[int index]
        {
            get
            {
                return this.data[index];
            }
            set
            {
                this.data[index] = value;
            }
        }

        #region properties

        /// <summary>
        /// Gets the dimensions of the vector as an array.
        /// </summary>
        public double[] Dimensions
        {
            get
            {
                return this.data;
            }
        }
        /// <summary>
        /// Gets the dimension of the vector.
        /// </summary>
        public int Dimension
        {
            get
            {
                return this.data.Length;
            }
        }
        /// <summary>
        /// Gets a value that determines the vector is a unit vector.
        /// </summary>
        public bool IsUnitVector
        {
            get
            {
                return this.Length == 1;
            }
        }

        /// <summary>
        /// Gets a value that determines the vector is a null vector.
        /// </summary>
        public bool IsNullVector
        {
            get
            {
                return this.Length == 0;
            }
        }

        /// <summary>
        /// Gets the euclidean norm.
        /// </summary>
        public double Length
        {
            get
            {
                return Math.Sqrt(this.data.Sum(n => n * n));// root of sum of squares => Pythagorean theorem
            }
        }

        /// <summary>
        /// Gets the sum of all dimensions.
        /// </summary>
        public double Sum
        {
            get
            {
                return this.data.Sum();
            }
        }

        #endregion

        #region math methods

        /// <summary>
        /// Adds two vectors and returns the result.
        /// </summary>
        /// <param name="v1">The first value to add.</param>
        /// <param name="v2">The second value to add.</param>
        /// <returns>The sum of <paramref name="v1"/> and <paramref name="v2"/>.</returns>
        public static Vector Add(Vector v1, Vector v2)
        {
            if (v1.Dimension != v2.Dimension)
                throw ExceptionHelper.GetArgumentDifferent("v1.Dimension", "v2.Dimension", v1.Dimension, v2.Dimension, "vectorNotSameDim");
            Vector result = new Vector(v1.Dimension);
            for (int i = 0; i < v1.Dimension; i++)
                result[i] = v1[i] + v2[i];
            return result;
        }
        /// <summary>
        /// Subtract one vectors from another and returns the result.
        /// </summary>
        /// <param name="v1">The value to subtract from (the minuend).</param>
        /// <param name="v2">The value to subtract (the subtrahend).</param>
        /// <returns>The result of subtracting <paramref name="v2"/> from <paramref name="v1"/>.</returns>
        public static Vector Subtract(Vector v1, Vector v2)
        {
            if (v1.Dimension != v2.Dimension)
                throw ExceptionHelper.GetArgumentDifferent("v1.Dimension", "v2.Dimension", v1.Dimension, v2.Dimension, "vectorNotSameDim");
            Vector result = new Vector(v1.Dimension);
            for (int i = 0; i < v1.Dimension; i++)
                result[i] = v1[i] - v2[i];
            return result;
        }
        /// <summary>
        /// Return the product of two vectors.
        /// </summary>
        /// <param name="v1">The first value to multiply.</param>
        /// <param name="v2">The second value to multiply.</param>
        /// <returns>The product of <paramref name="v1"/> and <paramref name="v2"/>.</returns>
        public static Vector Multiply(Vector v1, Vector v2)
        {
            if (v1.Dimension != v2.Dimension)
                throw ExceptionHelper.GetArgumentDifferent("v1.Dimension", "v2.Dimension", v1.Dimension, v2.Dimension, "vectorNotSameDim");
            Vector result = new Vector(v1.Dimension);
            for (int i = 0; i < v1.Dimension; i++)
                result[i] = v1[i] * v2[i];
            return result;
        }
        /// <summary>
        /// Return the product of one vectors and a scalar.
        /// </summary>
        /// <param name="vector">The vector to multiply.</param>
        /// <param name="scalar">The scalar to multiply.</param>
        /// <returns>The product of <paramref name="vector"/> and <paramref name="scalar"/>.</returns>
        public static Vector Multiply(Vector vector, double scalar)
        {
            Vector result = new Vector(vector.Dimension);
            for (int i = 0; i < vector.Dimension; i++)
                result[i] = vector[i] * scalar;
            return result;
        }
        /// <summary>
        /// Negates a vector.
        /// </summary>
        /// <param name="vector">The vector to negate.</param>
        /// <returns>The <paramref name="vector"/> parameter multiplied with negative one (-1).</returns>
        public static Vector Negate(Vector vector)
        {
            return new Vector(vector.data.Select(x => -x));
        }

        /// <summary>
        /// Raises the power of each dimension from a vector.
        /// </summary>
        /// <param name="base">The vector.</param>
        /// <param name="exponent">The exponent to raise <paramref name="base"/> by.</param>
        /// <returns>The result of raising each dimension from <paramref name="base"/> to the <paramref name="exponent"/> power.</returns>
        public static Vector Pow(Vector @base, double exponent)
        {
            return new Vector(@base.data.Select(x => Math.Pow(x, exponent)));
        }

        #endregion

        #region operators

        /// <summary>
        /// Adds two vectors and returns the result.
        /// </summary>
        /// <param name="v1">The first value to add.</param>
        /// <param name="v2">The second value to add.</param>
        /// <returns>The sum of <paramref name="v1"/> and <paramref name="v2"/>.</returns>
        public static Vector operator +(Vector v1, Vector v2)
        {
            return Add(v1, v2);
        }
        /// <summary>
        /// Subtract one vectors from another and returns the result.
        /// </summary>
        /// <param name="v1">The value to subtract from (the minuend).</param>
        /// <param name="v2">The value to subtract (the subtrahend).</param>
        /// <returns>The result of subtracting <paramref name="v2"/> from <paramref name="v1"/>.</returns>
        public static Vector operator -(Vector v1, Vector v2)
        {
            return Subtract(v1, v2);
        }
        /// <summary>
        /// Return the product of two vectors.
        /// </summary>
        /// <param name="v1">The first value to multiply.</param>
        /// <param name="v2">The second value to multiply.</param>
        /// <returns>The product of <paramref name="v1"/> and <paramref name="v2"/>.</returns>
        public static Vector operator *(Vector v1, Vector v2)
        {
            return Multiply(v1, v2);
        }
        /// <summary>
        /// Return the product of one vectors and a scalar.
        /// </summary>
        /// <param name="vector">The vector to multiply.</param>
        /// <param name="scalar">The scalar to multiply.</param>
        /// <returns>The product of <paramref name="vector"/> and <paramref name="scalar"/>.</returns>
        public static Vector operator *(Vector vector, double scalar)
        {
            return Multiply(vector, scalar);
        }
        /// <summary>
        /// Return the product of one vectors and a scalar.
        /// </summary>
        /// <param name="scalar">The scalar to multiply.</param>
        /// <param name="vector">The vector to multiply.</param>
        /// <returns>The product of <paramref name="vector"/> and <paramref name="scalar"/>.</returns>
        public static Vector operator *(double scalar, Vector vector)
        {
            return Multiply(vector, scalar);
        }
        /// <summary>
        /// Negates a vector.
        /// </summary>
        /// <param name="vector">The vector to negate.</param>
        /// <returns>The <paramref name="vector"/> parameter multiplied with negative one (-1).</returns>
        public static Vector operator -(Vector vector)
        {
            return Negate(vector);
        }

        /// <summary>
        /// Returns a value indicating whether a vector is equal to another vector. 
        /// </summary>
        /// <param name="v1">The first vector.</param>
        /// <param name="v2">The second vector.</param>
        /// <returns><c>true</c>, if <paramref name="v1"/> equals to <paramref name="v2"/>; otherwise <c>false</c>.</returns>
        public static bool operator ==(Vector v1, Vector v2)
        {
            if (v1.Dimension != v2.Dimension)
                return false;
            for (int i = 0; i < v1.data.Length; ++i)
                if (v1.data[i] != v2.data[i])
                    return false;
            return true;
        }
        /// <summary>
        /// Returns a value indicating whether a vector is not equal to another vector. 
        /// </summary>
        /// <param name="v1">The first vector.</param>
        /// <param name="v2">The second vector.</param>
        /// <returns><c>false</c>, if <paramref name="v1"/> equals to <paramref name="v2"/>; otherwise <c>true</c>.</returns>
        public static bool operator !=(Vector v1, Vector v2)
        {
            if (v1.Dimension != v2.Dimension)
                return true;
            for (int i = 0; i < v1.data.Length; ++i)
                if (v1.data[i] != v2.data[i])
                    return true;
            return false;
        }

        #endregion

        #region override

        /// <summary>
        /// Returns the string form of the vector.
        /// </summary>
        /// <returns>The string form of the vector.</returns>
        public override string ToString()
        {
            return string.Format("( {0} )", string.Join(" | ", this.data));
        }
        /// <summary>
        /// Returns the bitwise exclusive-OR combined hash code of the dimensions.
        /// </summary>
        /// <returns>The bitwise exclusive-OR combined hash code of the dimensions.</returns>
        public override int GetHashCode()
        {
            return this.data.Select(x => x.GetHashCode()).Xor();
        }

        /// <summary>
        /// Returns a value indicating whether this instance is equal to a specified object.
        /// </summary>
        /// <param name="obj">An object to compare with this instance. </param>
        /// <returns><c>true</c> if obj is an instance of <see cref="Vector"/> and equals the value of this instance; otherwise, <c>false</c>.</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is Vector) || ((Vector)obj).Dimension != this.Dimension)
                return false;
            for (int i = 0; i < this.data.Length; ++i)
                if (((Vector)obj).data[i] != this.data[i])
                    return false;
            return true;
        }

        #endregion

        //TODO: to matrix
    }
}
